﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;

namespace MDXModelImporter
{
    //e.g. in XLDM out MDLX	
    public class mdxTag
    {
        //static char[] tag;
        static public string Read(ref BinaryReader br)
        {                  
            //string strTag = ""+tag[0] + tag[1] + tag[2] + tag[3];
            return new string(br.ReadChars(4));
        }
    }
    
    public struct Layer1
    {
        int mLayerSize; // in Bytes
        int mBlendMode;
        int mUnk3;
        int mTexture;
        int mUnk5;
        int mUnk6;
        float mUnk7;		// alpha ?
        bool mKMTA;
    };

   public struct SequenceName
    {
	    char[]	mName;//80
	    int		mUnk1;
	    int		mUnk2;
	    float	mUnk3;
	    int		mUnk4;
	    float	mUnk5;
	    int		mUnk6;
	    float	mUnk7;
	    Vector3	mMins;
	    Vector3	mMaxs;
    };

   public struct TextureName
    {
	    UInt32	mUnk1;
        char[]  mName; //[MAX_PATH] 260
        UInt32  mUnk2;        
    };

   public struct Camera
    {
	    char[]  cName;//[80];
	    float   posx;
	    float   posy;
	    float   posz;
	    float   cFOV;
	    float   farClip;
	    float   nearClip;
	    float   targetx;
	    float   targety;
	    float   targetz;
    };

   public struct mdxVersion
    {    
	    UInt32		mVersion;  	    
    };

   public struct mdxInfo
    {    
	    string	    mName;
	    string	    mName2;
        float       BoundsRadius;
	    Vector3 	mMins;
        Vector3     mMaxs;
        UInt32      BlendTime;

       public void Read(ref BinaryReader br,int size)
       {
           if (size != 372)
           {
               string str="Error mdxInfo is not 372";
               Log.Write(str);
               throw new Exception(str);
           }

           mName = new string(br.ReadChars(80));
           mName2 = new string(br.ReadChars(260));//#define MAX_PATH   260
           BoundsRadius = br.ReadSingle();

           mMins = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
           mMaxs = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
           BlendTime = br.ReadUInt32();

           Log.Write("mName:" + mName.Substring(0,10)/* + "\t mName2:" + mName2*/ 
               +"\t BoundsRadius:" + BoundsRadius + "\t BleandTime:" + BlendTime + "\r\n"
               + mMins + "\t" + mMaxs
               );
           
       }
    };

    public struct AnimExtent
    {
        public Vector3 MinimumExtent;
        public Vector3 MaximumExtent;
        public float BoundsRadius;
    };

    public class mdxGeoChunk
    {
        int         numVertices;
        Vector3[]   Vertices;
        Vector3[]   Normals;
        int         numTriangles;        
        short[]     triangles;
        char[]      vertexGroups;
        int         numGroups;
        int[]       groups;
        int         numMatrixGroups;
        int[]       matrixGroups;
        
        int         materialID;
        float boundsRadius;           
        Matrix[] matrixes;        
        Vector3 mins;
        Vector3 maxs;
        public Vector3 MinimumExtent
        {
            get { return mins; }
        }
        public Vector3 MaximumExtent
        {
	        get { return maxs;}
        }
        int numAnimExtent;
        AnimExtent[] animExtents;        
        float[] AnimAlphas;
        Vector2[] uvs;

        public void Read(BinaryReader br, int inSize)
        {
            int chunkSize, n = 0;
            long startPos = br.BaseStream.Position, brOffeset;
            #region While n<8 read geometry data
            while ((br.BaseStream.Position < startPos + inSize) && (n < 8))
            {
                string tagStr = mdxTag.Read(ref br);
                Log.Write("GeoChunk - " + tagStr);            
                switch (tagStr)
                {
                    case "VRTX": // vertex                      
                        numVertices = br.ReadInt32();
                        Log.Write("numVertices = " + numVertices);
                        brOffeset = br.BaseStream.Position;
                        Vertices = new Vector3[numVertices];
                        for (int i = 0; i < numVertices; i++)
                        {
                            Vertices[i] = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                        }
                        n++;
                        br.BaseStream.Position = brOffeset + 12 * numVertices;
                        break;

                    case "NRMS": // normal                        
                        int NumNormals = br.ReadInt32();                        
                        brOffeset = br.BaseStream.Position;
                        if (NumNormals != numVertices)
                        {
                            throw new Exception("NumNormals != mNumVertices");
                        }
                        Normals = new Vector3[NumNormals];
                        for (int i = 0; i < NumNormals; i++)
                        {
                            Normals[i] = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                        }
                        n++;
                        br.BaseStream.Position = brOffeset + 12 * NumNormals;
                        break;
                    case "PTYP": // PLANE_TYPE
                        int size = br.ReadInt32();                       
                        brOffeset = br.BaseStream.Position;
                        int type = br.ReadInt32();
                        if (type != 4)// == Triangles
                        {
                            Log.Write("not triangle The Type is " + size);
                            throw new Exception("not Triangles");
                        }
                        br.BaseStream.Position = brOffeset + size * 4;
                        n++;
                        break;
                    case "PCNT": // primitives count
                        size = br.ReadInt32();                        
                        brOffeset = br.BaseStream.Position;
                        if (size == 1)
                            numTriangles = br.ReadInt32() / 3;
                        else
                        {
                            numTriangles = size;
                            br.BaseStream.Position = brOffeset + size * 4;
                        }
                        n++;
                        Log.Write("numTriangles=" + numTriangles);
                        break;
                    case "PVTX": // primitives vertices 
                        size = br.ReadInt32();
                        Log.Write("primitives vertices  " + size);
                        if (size / 3 != numTriangles)
                        {
                            Log.Write("Tag " + tagStr + " Error VerticeNum is " + size + " do not match the numTriangles*3");
                            throw new Exception("Tag " + tagStr + " Error VerticeNum is " + size + " do not match the numTriangles*3");
                        }                        
                        brOffeset = br.BaseStream.Position;
                        triangles = new short[size];
                        for (int i = 0; i < numTriangles; i++)
                            triangles[i] = br.ReadInt16();
                        br.BaseStream.Position = brOffeset + size * sizeof(short);
                        n++;
                        break;
                    case "GNDX": // vertex group indices links every vertex to a matrix   
                        size = br.ReadInt32();
                        Log.Write("vertex group indices num=" + size);
                        if (numVertices != size)
                        {
                            throw new Exception("Tag " + tagStr + " Error VerticeNum is " + size + " do not match the numTriangles*3");
                        }    
                        brOffeset = br.BaseStream.Position;
                        vertexGroups = new char[size];
                        for (int i = 0; i < numMatrixGroups; i++)
                            vertexGroups[i] = br.ReadChar();
                        br.BaseStream.Position = brOffeset + size;
                        n++; 
                        break;
                        
                    case "MTGC": // group matrix counts this is the number of vertices defined by GNDX for each matrix		
                        numGroups = br.ReadInt32();
                        Log.Write("numGroups= " + numGroups);
                        brOffeset = br.BaseStream.Position;
                        groups = new int[numGroups];
                        for (int i = 0; i < numMatrixGroups; i++)
                            groups[i] = br.ReadInt16();
                        br.BaseStream.Position = brOffeset + 4 * numGroups;
                        n++;
                        break;
                    case "MATS": // matrices                                        
                        numMatrixGroups = br.ReadInt32();
                        Log.Write("numGroups=" + numGroups);
                        brOffeset = br.BaseStream.Position;
                        matrixGroups = new int[numMatrixGroups];
                        for (int i = 0; i < numMatrixGroups; i++)
                            matrixGroups[i] = br.ReadInt16();
                        br.BaseStream.Position = brOffeset + 4 * numMatrixGroups;
                        n++;
                        break;
                    
                    default:                        
                        chunkSize = br.ReadInt32();
                        Log.Write("Unhandle Tag " + tagStr + "Length=" + chunkSize);   
                        brOffeset = br.BaseStream.Position;
                        br.BaseStream.Position = brOffeset + chunkSize;
                        break;
                }
            }            
            #endregion
            #region rean uv  
            Log.Enable = true;
            if (br.BaseStream.Position < startPos + inSize)
            {
                materialID = br.ReadInt32();
                br.BaseStream.Position += 8;//unknow
                boundsRadius = br.ReadSingle();
                mins = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                maxs = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                Log.Write("materialID=" + materialID + "\tboundsRadius=" + boundsRadius);
                numAnimExtent = br.ReadInt32();
                animExtents = new AnimExtent[numAnimExtent];
                for (int i = 0; i < numAnimExtent; i++)
                {
                    animExtents[i] = new AnimExtent();
                    animExtents[i].MinimumExtent = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                    animExtents[i].MaximumExtent = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                    animExtents[i].BoundsRadius = br.ReadSingle();
                }

                if (mdxTag.Read(ref br) == "UVAS")
                {
                    br.BaseStream.Position += 4;//  skip 4 bytes
                    if (mdxTag.Read(ref br) == "UVBS")
                    {                   
                        int num = br.ReadInt32();
                        Log.Write("UVBS num=" + num);
                        uvs = new Vector2[num];
                        for (int i = 0; i < num; i++)
                        {
                            uvs[i] = new Vector2(br.ReadSingle(), br.ReadSingle());
                        }
                    }
                }

                //Log.Write("numAnimExtent=" + numAnimExtent + "\t uvs=" + uvs[0]);
            }
            Log.Enable = false;
            #endregion
        }

        private void CalcGroupMatrix(MDXParse model)
        {
            if (matrixes == null)
            {
                matrixes = new Matrix[numGroups];
                AnimAlphas = new float[numGroups];

            }

            int index = 0;
            for (int i = 0; i < numGroups; i++)
            {
                int matrixCount = groups[i];
                //if (i == 27)
                //    i = i;
                //model->skeleton.GetTransformMatrix(matrixes[i], AnimAlphas[i], &matrixGroups[index], matrixCount);
                if (AnimAlphas[i] < 0.5f)
                    AnimAlphas[i] = AnimAlphas[i];
                index += matrixCount;
            }
        }


    }

    public class mdxGeometry
    {        
	    int				mNumChunks;
	    mdxGeoChunk[]	mChunks;//MAX_CHUNKS=512;
	    //bool			mLoaded;
        //mNumChunks(NULL),mLoaded(false)		
        
        public void Read(ref BinaryReader br, int inSize)
        {
            //if (mLoaded)
            //{
            //    return;
            //}
            //mLoaded = true;

            mChunks = new mdxGeoChunk[512];//MAX_CHUNKS=512;
            long startPos = br.BaseStream.Position ,brOffeset;
            int chunkSize;
            mNumChunks = 0;
            while (br.BaseStream.Position < startPos + inSize)
            {
                Log.Write("Chunck[" + mNumChunks + "]\t");
                chunkSize = br.ReadInt32() - 4;
                brOffeset=br.BaseStream.Position;
                mChunks[mNumChunks].Read(br, chunkSize);
                br.BaseStream.Position = brOffeset + chunkSize;                
                mNumChunks++;
            }
            Log.Write("Geometry Chunck Number:" + mNumChunks + "\t");
        }


    };

    public struct mdxTextures
    {
	    int				mNumTextures;
	    TextureName		mTextures;
	    bool			mLoaded;
	    int[]			mGLBind;//[384];

		//mNumTextures(0),mLoaded(0) {}	    
    };

    public struct mdxSequence
    {
        int				mNumSequences;
	    SequenceName[]	mSequences;
	    bool			mLoaded;
    	
		// mNumSequences(0),mLoaded(0) {}
    };

    public struct mdxMaterial
    {   
	    int				mUnk1;
	    int				mUnk2;
	    // 'LAYS'	
	    int				mNumLayers;
	    Layer1[]		mLayers;// MAX_LAYER 32
    };

    public struct mdxMaterialMap
    {
	    bool			mLoaded;
	    int				mNumMaterials;
	    mdxMaterial[]	mMaterials;//MAX_MATERIALS 128
    	
	    //mLoaded(0) {}
    };


    public struct mdxCameras
    {
	    int				mNumCameras;
	    Camera[]		mCameras;//MAX_CAMERAS 32
	    bool			mLoaded;
	    //mNumCameras(NULL),mLoaded(false)	{ }
    };

    //line near 
    class LinearKeyFrame3
    {
        int frameNum;
        Vector3 vec;
        public LinearKeyFrame3(int inFramNum,Vector3 inVec)
        {
            frameNum = inFramNum;
            vec = inVec;
        }
    };
    class NonLinearKeyFrame3
    {
        int frameNum;
        Vector3 vec;
        Vector3 inTan;
        Vector3 outTan;
        public NonLinearKeyFrame3(int inFramNum, Vector3 inVec, Vector3 inInTan, Vector3 inOutTan)
        {
            frameNum = inFramNum;
            vec = inVec;
            inTan= inInTan;
            outTan = inOutTan;
        }
    };
    class LinearKeyFrame4
    {
        int frameNum;
        Quaternion vec;
        public LinearKeyFrame4(int inFramNum,Quaternion inVec)
        {
            frameNum = inFramNum;
            vec = inVec;
        }
    };
    class NonLinearKeyFrame4
    {
        int frameNum;
        Quaternion vec;
        Quaternion inTan;
        Quaternion outTan;
        public NonLinearKeyFrame4(int inFramNum, Quaternion inVec, Quaternion inInTan, Quaternion inOutTan)
        {
            frameNum = inFramNum;
            vec = inVec;
            inTan= inInTan;
            outTan = inOutTan;
        }
    };
 public class CBone
{     
    public int count;
	//Linear Type (0:don't interpret;1:linear;2:hermite;3:bezier)
	public enum LineType
    {
		NOT_INTERP=0,
		LINEAR,
		HERMITE,
		BEZIER
	};
	public enum MotionType{
		KGTR=0,
		KGRT,
		KGSC,
		MAX_MOTION
	};

	public int			objectID;
	public int			parentID;
	public Matrix	transformMatrix;
     string boneName;
	int			nodeType;
	//key frames count
	int[]				keyFrameCount;//MAX_MOTION=3
	//Linear Type 
	int	[]				lineType;//MAX_MOTION
	object[]				keyFrames;//MAX_MOTION
	int			geosetID;
	int			geosetAnimID;
	float		geosetAnimAlpha;

     CBone()
     {
         count = -1;
         boneName = null;
         objectID = -1;
         parentID = -1;
         geosetID = -1;
         geosetAnimID = -1;
         geosetAnimAlpha = 1.0f;
         transformMatrix = Matrix.Identity;

         for (int i = 0; i < Convert.ToInt32( MotionType.MAX_MOTION); i++)//MAX_MOTION=3
         {
             keyFrameCount[i] = 0;
             lineType[i] = 0;
             keyFrames[i] = null;
         }
     }

     private void ReadVectero3Transfrom(ref BinaryReader br, int motionType)
     {
         keyFrameCount[motionType] = br.ReadInt32();
         lineType[motionType] = br.ReadInt32();
         br.ReadInt32();
         //non linear trasfer
         if (lineType[motionType] > Convert.ToInt32(LineType.LINEAR))
         {
             keyFrames = new NonLinearKeyFrame3[keyFrameCount[motionType]];
             for (int i = 0; i < keyFrameCount[motionType]; i++)
             {
                 keyFrames[i] = new NonLinearKeyFrame3(
                     br.ReadInt32(),
                     new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()),
                     new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()),
                     new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle())
                     );
             }
         }
         else
         {
             keyFrames = new LinearKeyFrame3[keyFrameCount[motionType]];
             for (int i = 0; i < keyFrameCount[motionType]; i++)
             {
                 keyFrames[i] = new LinearKeyFrame3(
                     br.ReadInt32(),
                     new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle())
                     );
             }
         }
     }

     private void ReadQuaterationTransfrom(ref BinaryReader br, int motionType)
     {
         keyFrameCount[motionType] = br.ReadInt32();
         lineType[motionType] = br.ReadInt32();
         br.ReadInt32();
         //non linear trasfer
         if (lineType[motionType] > Convert.ToInt32(LineType.LINEAR))
         {
             keyFrames = new NonLinearKeyFrame4[keyFrameCount[motionType]];
             for (int i = 0; i < keyFrameCount[motionType]; i++)
             {
                 keyFrames[i] = new NonLinearKeyFrame4(
                     br.ReadInt32(),
                     new Quaternion(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()),
                     new Quaternion(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()),
                     new Quaternion(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle())
                     );
             }
         }
         else
         {
             keyFrames = new LinearKeyFrame4[keyFrameCount[motionType]];
             for (int i = 0; i < keyFrameCount[motionType]; i++)
             {
                 keyFrames[i] = new LinearKeyFrame4(
                     br.ReadInt32(),
                     new Quaternion(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle())
                     );
             }
         }
     }

     public void Read(BinaryReader br, int inSize)
     {
         int iread = 0;

         boneName =new string(br.ReadChars(80));
         objectID = br.ReadInt32();
         //if (objectID == 17)
         //    objectID = 17;
         parentID = br.ReadInt32();
         nodeType = br.ReadInt32();
         //char* nextAddr = inP.c + inSize;
         
         long startPos = br.BaseStream.Position, brOffeset;
                    
         while ((br.BaseStream.Position < startPos + inSize) && (iread < 3))
         {
             string tagStr = mdxTag.Read(ref br);
             Log.Write("Bone - " + tagStr);
                switch (tagStr)
                {
                    case "KGTR": // vertex                        
                        int motionType = Convert.ToInt32(MotionType.KGTR);
                        ReadVectero3Transfrom(ref br,motionType);
                        iread++;
                        break;

                    case "KGRT":                       
                        motionType = Convert.ToInt32(MotionType.KGRT);
                        ReadQuaterationTransfrom(ref br, motionType);
                        iread++;
                        break;

                    case "KGSC":                        
                        motionType = Convert.ToInt32(MotionType.KGSC);
                        ReadVectero3Transfrom(ref br, motionType);
                        iread++;
                        break;
                }
         }

         if ((br.BaseStream.Position < startPos + inSize-4)// p.c < inP.c + inSize - 4)
         {
             geosetID = br.ReadInt32();;
             geosetAnimID =  br.ReadInt32();;
         }
         
     }

    //    int			GetGeosetID()		{ return geosetID; };
    //    int			GetGeosetAnimID()	{ return geosetAnimID; };
    //    void		SetGeosetAlpha(float alpha)	{geosetAnimAlpha=alpha;};
    //    float		GetGeosetAlpha()	{return geosetAnimAlpha;};

    //    char*		GetBoneName()	{ return boneName; };
    //    int			GetObjectID()	{ return objectID; };
    //    int			GetParentID()	{ return parentID; };

    //    D3DXMATRIX*	GetTransformMatrix() { return &transformMatrix; };
    //    D3DXMATRIX*	CalcTransformMatrix(D3DXVECTOR3* center, const AnimInfo& animInfo,const D3DXMATRIX* parentMatrix);

    //    D3DXMATRIX* TransformMatrixMultiply(const D3DXMATRIX* matrix);

    //private:
    //bool			GetTransferMatrix(D3DXVECTOR3* vec,const AnimInfo& animInfo);
    //bool			GetRotationMatrix(D3DXQUATERNION* quat,const AnimInfo& animInfo);
    //bool			GetScaleMatrix(D3DXVECTOR3* vec,const AnimInfo& animInfo);
    //bool			CheckHideByScale(const AnimInfo& animInfo);

};
}
